package com.sinotrans.common.aop;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.plugin.core.PluginRegistry;

import com.sinotrans.common.security.AuthEntity;
import com.sinotrans.common.security.AuthUtils;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import springfox.documentation.oas.mappers.ServiceModelToOpenApiMapper;
import springfox.documentation.oas.web.OpenApiTransformationContext;
import springfox.documentation.oas.web.WebMvcOpenApiTransformationFilter;
import springfox.documentation.service.Documentation;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.spring.web.json.JsonSerializer;
import springfox.documentation.spring.web.plugins.Docket;

/**
 * Swagger切面
 * 
 * @author gaofu
 *
 */
@Aspect
@Order(2)
@RefreshScope
public class SwaggerApiAspect {
	protected static final Logger logger = LoggerFactory.getLogger(SwaggerApiAspect.class);
	public static final String SWAGGER_POINTCUT = "execution(* springfox.documentation.oas.web.OpenApiControllerWebMvc.getDocumentation(..))";

	@Value(value = "/${spring.application.name}")
	protected String application;

	@Autowired
	private DocumentationCache documentationCache;
	@Autowired
	private ServiceModelToOpenApiMapper mapper;
	@Autowired
	private JsonSerializer jsonSerializer;

	@Autowired
	@Qualifier("webMvcOpenApiTransformationFilterRegistry")
	private PluginRegistry<WebMvcOpenApiTransformationFilter, DocumentationType> transformations;

	private volatile Constructor<?> openApiTransConstructor = null;

	private Object lock = new Object();

	@Around(SWAGGER_POINTCUT)
	public Object swaggerAround(ProceedingJoinPoint pjp) throws Throwable {
		// 参数
		Object[] args = pjp.getArgs();
		String group = (String) args[0];
		HttpServletRequest servletRequest = (HttpServletRequest) args[1];
		//
		String groupName = Optional.ofNullable(group).orElse(Docket.DEFAULT_GROUP_NAME);
		Documentation documentation = documentationCache.documentationByGroup(groupName);
		if (documentation == null) {
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		}
		OpenAPI oas = mapper.mapDocumentation(documentation);
		OpenApiTransformationContext<HttpServletRequest> context = openApiTransformationContext(oas, servletRequest);
		List<WebMvcOpenApiTransformationFilter> filters = transformations.getPluginsFor(DocumentationType.OAS_30);
		for (WebMvcOpenApiTransformationFilter each : filters) {
			context = context.next(each.transform(context));
		}
		//
		try {
			AuthEntity authEntity = AuthUtils.getAuthorize();
			if (authEntity != null && AuthUtils.GATEWAY.equalsIgnoreCase(authEntity.getChn())) {
				Paths paths = oas.getPaths();
				Paths ipaths = new Paths();
				for (Entry<String, PathItem> entry : paths.entrySet()) {
					String uri = application + entry.getKey();
					ipaths.put(uri, entry.getValue());
				}
				oas.setPaths(ipaths);
			}
		} catch (Exception e) {
			logger.error("SwaggerApiAspect error.", e);
		}
		//
		return new ResponseEntity<>(jsonSerializer.toJson(oas), HttpStatus.OK);
	}

	private OpenApiTransformationContext<HttpServletRequest> openApiTransformationContext(OpenAPI oas,
			HttpServletRequest servletRequest) throws Exception {
		Constructor<?> constructor = getOpenApiTransConstructor();
		@SuppressWarnings("unchecked")
		OpenApiTransformationContext<HttpServletRequest> context = (OpenApiTransformationContext<HttpServletRequest>) constructor
				.newInstance(oas, servletRequest);
		return context;
	}

	private Constructor<?> getOpenApiTransConstructor() {
		if (openApiTransConstructor == null) {
			synchronized (lock) {
				if (openApiTransConstructor == null) {
					try {
						Class<?> clazz = Class.forName("springfox.documentation.oas.web.OpenApiTransformationContext");
						// 获取有参构造
						openApiTransConstructor = clazz.getDeclaredConstructor(OpenAPI.class, Object.class);
						// 允许访问这个构造方法
						openApiTransConstructor.setAccessible(true);
					} catch (Exception e) {
						logger.error("getOpenApiTransConstructor error.", e);
					}
				}
			}
		}
		return openApiTransConstructor;
	}

}
